INTRODUCTION

Giới thiệu

Khách sạn trực tiếp tạo ra doanh thu cho các nền kinh tế địa phương khi khách du lịch chi tiền trong khách sạn, nhà hàng và địa điểm giải trí.
Ngành khách sạn đang phát triển, ngày càng có nhiều người chi tiền cho các hoạt động nghỉ dưỡng và giải trí.
Mọi người chỉ có thể vào khách sạn khi đó là một kỳ nghỉ lễ hoặc một sự kiện đặc biệt, do đó nhu cầu ở trong phòng không phân bổ đều trong năm.
Ngành khách sạn là một ngành rất biến động và việc đặt phòng phụ thuộc vào nhiều yếu tố như loại khách sạn, tính thời vụ, ngày trong tuần, v.v.
Điều này làm cho việc phân tích các mẫu có sẵn trong dữ liệu cũ trở nên quan trọng hơn để giúp các khách sạn lập kế hoạch tốt hơn.
Sử dụng dữ liệu cũ, các khách sạn có thể thực hiện các chiến dịch khác nhau để thúc đẩy hoạt động kinh doanh.
Dữ liệu bao gồm khoảng 119.390 giao dịch đặt phòng từ 2 khách sạn: một khách sạn thành phố từ Lisbon (“City Hotel”) và một khách sạn nghỉ dưỡng từ Algarve (“Resort Hotel”).
Bộ dữ liệu bao gồm các lượt đặt trước sẽ đến trong khoảng thời gian từ ngày 1 tháng 7 năm 2015 đến ngày 31 tháng 8 năm 2017, bao gồm các lượt đặt trước đã đến và các lượt đặt trước đã bị hủy.
Có rất nhiều điều để khám phá từ dữ liệu này. Nhóm sẽ đặt ra 5 câu hỏi như sau:

  • Các nguồn đặt phòng đến từ đâu?

  • Tần suất hủy bỏ đối với khách hàng và khả năng họ không đến nhận phòng là bao nhiêu?

  • Xu hướng về mức giá có thể tính cho khách hàng là bao nhiêu?

  • Khách sạn có thể kiếm được bao nhiêu doanh thu trên mỗi lần đặt phòng và bao nhiêu tiền đến từ mỗi quốc gia?

  • Khách hàng có quay trở lại khách sạn hay không?

Đồng thời dự đoán lượt đặt phòng có bị hủy bỏ hay không.

Đề xuất phương pháp phân tích

Số lượng đặt phòng theo quốc gia và kênh phân phối sẽ được trả lời các lượt đặt trước đến từ đâu.
Tần suất hủy phòng và không đến nhận phòng sẽ được đánh giá bằng cách chia số lượng đặt phòng bị hủy cho tổng số lượng đặt phòng.
Tỷ lệ hủy phòng sẽ được tính theo quốc gia để xác định quốc gia nào có tỷ lệ hủy phòng cao nhất.
Doanh thu sẽ được tính từ dữ liệu đặt phòng. Điều này sẽ liên quan đến việc nhân thời gian lưu trú với tỷ lệ trung bình cho khách.
Doanh thu thu được từ các đặt phòng bị hủy sẽ được lập bảng.
Doanh thu đặt phòng trung bình sẽ được sử dụng để so sánh các đặt phòng từ các quốc gia khác nhau.
Phát triển một mô hình dự đoán đặt phòng có hủy và không hủy bằng cách sử dụng hồi quy logistic.

Mục đích phân tích

Việc phân tích nhằm mục đích đạt được cái nhìn sâu sắc thú vị về hành vi của khách hàng khi đặt phòng khách sạn. Để tối ưu hóa doanh thu mà khách sạn đạt được, ban quản lý thường sử dụng chiến lược định giá, một trong số đó là tăng giá phòng khi nhu cầu cao và khuyến mãi khi nhu cầu thấp.
Do đó, khả năng dự báo chính xác nhu cầu trong tương lai là rất quan trọng và trở thành một phần quan trọng trong kế hoạch định giá.
Nhu cầu đối với các phân khúc khách hàng khác nhau có thể khác nhau và việc dự báo trở nên khó khăn hơn vì nó có thể yêu cầu các mô hình khác nhau cho các phân khúc khác nhau. Những thông tin chi tiết này có thể hướng dẫn các khách sạn điều chỉnh chiến lược khách hàng của họ và chuẩn bị cho những điều chưa biết.

SET UP PACKAGES

Liệt kê các package nhóm đã sử dụng cho quá trình thực hiện project

library(tidyverse)
library(knitr)
library(lubridate)
library(patchwork)
library(ISOcodes)
library(maps)
library(forecast)
library(tseries)
library(ggpubr)
library(plotly)
library(glue)
library(scales)
library(caret)
library(ROCR)
Table 1: Package Summaries
Packages Version Summary
tidyverse 1.3.2 Các công cụ R cho thao tác và trực quan hóa dữ liệu
knitr 1.4.0 Tạo dymamic report, đặc biệt là hiển thị bảng
lubridate 1.8.0 Package xử lý ngày tháng
ISOcodes 2022.09.29 Chứa tập dữ liệu được sử dụng để dịch mã quốc gia ISO thành tên quốc gia
patchwork 1.1.2 Package để kết hợp nhiều ggplot2 thành một hình
maps 3.4.1 Vẽ biểu đồ địa lý
forecast 8.19 Dùng cho time series và linear models
ggpubr 0.5.0 Tạo biểu đồ dựa trên ggplot2 đẹp mắt hơn
plotly 4.10.1 Tạo biểu đồ tương tác
glue 1.6.2 Cung cấp các chuỗi ký tự được giải thích nhỏ, nhanh và không phụ thuộc
scales 1.2.1 Cung cấp công cụ mở rộng trong ggplot2
ROCR 1.0-11 Tạo đường cong ROC
caret 6.0-93 Dùng để phân vùng data

DATA DICTIONARY

Data Dictionary mô tả chi tiết các biến có trong dataset mà nhóm đã sử dụng

Table 2: Data Description
Variable Type Description
hotel factor Loại khách sạn (City Hotel or Resort Hotel)
is_canceled integer Cho biết đặt chỗ có bị hủy (1) hay không (0)
lead_time integer Số ngày từ khi đặt phòng đến khi khách đến khách sạn. Được tính bằng cách trừ ngày đặt phòng từ ngày đến
arrival_date_year integer Năm của ngày đến
arrival_date_month factor Tháng của ngày đến
arrival_date_week_number integer Ngày đến thuộc tuần thứ mấy trong năm
arrival_date_day_of_month integer Ngày của ngày đến
stays_in_weekend_nights integer Số đêm cuối tuần (thứ bảy hoặc chủ nhật) mà khách lưu trú hoặc đặt phòng để lưu trú tại khách sạn
stays_in_week_nights integer Số đêm trong tuần (từ thứ Hai đến thứ Sáu) mà khách lưu trú hoặc đặt phòng để lưu trú tại khách sạn
adults integer Số người lớn
children integer Số trẻ em
babies integer Số trẻ sơ sinh
meal factor Loại bữa ăn đã đặt. Không xác định/SC - không có gói bữa ăn; BB - Giường & Bữa sáng; HB - Bao ăn nửa bữa (bữa sáng và một bữa ăn khác - thường là bữa tối); FB - Bao ăn 3 bữa (sáng, trưa và tối)
country factor Quốc gia. Các danh mục được trình bày ở định dạng ISO 3155–3: 2013
market_segment factor Chỉ định phân khúc thị trường. “TA” - Đại lý du lịch (Bán lẻ các gói du lịch của TO hoặc bên thứ ba); “TO” - Nhà điều hành tour (công ty kinh doanh lữ hành, một đơn vị kinh doanh chuyên sắp xếp các dịch vụ du lịch riêng lẻ thành một sản phẩm du lịch hoàn chỉnh để bán cho khách du lịch)
distribution_channel factor Kênh phân phối đặt trước. Thuật ngữ “TA” có nghĩa là “Đại lý du lịch” và “TO” có nghĩa là “Nhà điều hành tour”
is_repeated_guest integer Cho biết đây có phải khách hàng quay lại/ khách hàng đã đặt phòng trước đây rồi (1) hay không (0)
previous_cancellations integer Số lần khách hàng đã hủy đặt chỗ trong quá khứ
previous_bookings_not_canceled integer Số lần đặt chỗ trước đó không bị khách hàng hủy trước lượt đặt chỗ hiện tại
reserved_room_type factor Mã loại phòng đã đặt trước. Trình bày dưới dạng mã thay vì nêu rõ vì lý do ẩn danh.
assigned_room_type factor Mã cho loại phòng được chỉ định cho đặt phòng. Đôi khi loại phòng được chỉ định khác với loại phòng đã đặt trước vì lý do hoạt động của khách sạn (ví dụ: đặt trước quá nhiều) hoặc do yêu cầu của khách hàng. Trình bày dưới dạng mã thay vì nêu rõ vì lý do ẩn danh.
booking_changes integer Số thay đổi/sửa đổi đối với đặt phòng kể từ thời điểm đặt phòng được nhập trên PMS cho đến thời điểm nhận phòng hoặc hủy bỏ
deposit_type factor Cho biết khách hàng có đặt cọc trước hay không và đặt cọc trước với kiểu như thế nào. Biến này có thể giả định 3 loại: No deposit - không đặt cọc trước; No Refund - trả trước toàn bộ chi phí; Refundable - trả trước một phần chi phí
agent factor ID của đại lý du lịch thực hiện việc đặt phòng
company factor ID của công ty/tổ chức đã thực hiện đặt phòng hoặc chịu trách nhiệm thanh toán đặt phòng.
days_in_waiting_list integer Số ngày đặt chỗ trong danh sách chờ trước khi nó được xác nhận cho khách hàng
customer_type factor Loại đặt phòng. Có 4 loại: Contract - đặt phòng có hợp đồng; Group - đặt phòng có liên quan đến một nhóm; Transient - Đặt phòng không thuộc hợp đồng hoặc nhóm và không liên quan đến đặt phòng tạm thời khác; Transient-party - đặt phòng tạm thời và có liên quan đến đặt phòng tạm thời khác
adr numeric Giá trung bình hàng ngày được xác định bằng cách chia tổng của tất cả các giao dịch lưu trú cho tổng số đêm lưu trú
required_car_parking_spaces integer Số lượng chỗ đậu xe ô tô theo yêu cầu của khách hàng
total_of_special_requests integer Số lượng yêu cầu đặc biệt của khách hàng (ví dụ: 2 giường đơn hoặc tầng cao)
reservation_status factor Cho biết khách hàng có hủy đặt chỗ hay không (“Canceled”); khách đã nhận phòng và sau đó trả phòng (“Check-Out”); hoặc không bao giờ xuất hiện mà không có lời giải thích với khách sạn (“No-Show”)
reservation_status_date factor Biến này có thể được sử dụng cùng với Trạng thái đặt chỗ (reservation_status) để biết khi nào đặt phòng bị hủy hoặc khi nào khách hàng đã trả phòng khách sạn

DATA PREPARATION

Data Preparation mô tả quá trình chuẩn bị và xử lý dữ liệu bao gồm nhập dữ liệu, xử lý dữ liệu bị thiếu, cấu trúc lại một số biến, sử dụng boxplot, histogram kết hợp thực tiễn xác định outlier để loại bỏ. Vì số lượng biến lớn nên nhóm sẽ gom các biến (variables) lại thành các tiểu mục (subsection) để dễ quản lý và thuận tiện cho quá trình xử lý, phân tích. Các subsection bao gồm: Arrival Date, Guest Demographics, Reservation Status, User Karma, Guest Accommodations, Booking Information, Average Daily Rate

Trong đó:

  • Arrival Date: chứa các biến liên quan đến ngày đến nhận phòng

  • Guest Demographic: chứa các biến liên quan đến đặc điểm của khách hàng (đối tượng/ độ tuổi, quốc gia)

  • Reservation Status: chứa các biến liên quan đến trạng thái đặt và nhận phòng

  • User Karma: chứa các biến liên quan đến lịch sử đặt phòng của khách hàng

  • Guest Accommodations: chứa các biến liên quan đến lưu trú của khách hàng (các yêu cầu đặc biệt, số đêm lưu trú,… )

  • Booking Information: chứa các biến liên quan đến việc đặt phòng (các kênh đặt phòng, loại khách sạn, loại phòng, …)

  • Average Daily Rate

Bảng 3 dưới dây mô tả các subsection mà nhóm đã chia :

Table 3: Subsection Variables
Subsection Variables
Arrival Date arrival_date_year, arrival_date_month, arrival_date_week_number, arrival_date_day_of_month
Guest Demographics adults, children, babies, country
Reservation Status reservation_status, reservation_status_date, is_canceled, lead_time, days_in_waiting_list
User Karma is_repeated_guest, previous_cancellations, previous_bookings_not_cancelled
Guest Accommodations stays_in_weekend_nights, stays_in_week_nights, reserved_room_type, assigned_room_type, meals, required_car_parking_spaces, total_of_special_requests
Booking Information hotel, market_segment, distribution_channel, booking_changes, deposit_type, agent, company, customer_type
Average Daily Rate adr

Import Data

Dữ liệu ban đầu từ bài báo “Hotel Booking Demand Datasets” được viết bởi Nuno Antonio, Ana Almeida, and Luis Nunes, February 2019.[1] Nhóm đã tải xuống dữ liệu đã được làm sạch từ #TidyTuesday.[2]

#load tập dữ liệu
hotels <- read_csv("data/hotels.csv")

Data Structure

Tập dữ liệu có 119390 quan sát và 32 biến. Một số biến có kiểu dữ liệu số, một số khác có kiểu dữ liệu ký tự (character), ngày tháng (date)

#Xem cấu trúc của dữ liệu
glimpse(hotels)
## Rows: 119,390
## Columns: 32
## $ hotel                          <chr> "Resort Hotel", "Resort Hotel", "Resort…
## $ is_canceled                    <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, …
## $ lead_time                      <dbl> 342, 737, 7, 13, 14, 14, 0, 9, 85, 75, …
## $ arrival_date_year              <dbl> 2015, 2015, 2015, 2015, 2015, 2015, 201…
## $ arrival_date_month             <chr> "July", "July", "July", "July", "July",…
## $ arrival_date_week_number       <dbl> 27, 27, 27, 27, 27, 27, 27, 27, 27, 27,…
## $ arrival_date_day_of_month      <dbl> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, …
## $ stays_in_weekend_nights        <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, …
## $ stays_in_week_nights           <dbl> 0, 0, 1, 1, 2, 2, 2, 2, 3, 3, 4, 4, 4, …
## $ adults                         <dbl> 2, 2, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, …
## $ children                       <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, …
## $ babies                         <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, …
## $ meal                           <chr> "BB", "BB", "BB", "BB", "BB", "BB", "BB…
## $ country                        <chr> "PRT", "PRT", "GBR", "GBR", "GBR", "GBR…
## $ market_segment                 <chr> "Direct", "Direct", "Direct", "Corporat…
## $ distribution_channel           <chr> "Direct", "Direct", "Direct", "Corporat…
## $ is_repeated_guest              <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, …
## $ previous_cancellations         <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, …
## $ previous_bookings_not_canceled <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, …
## $ reserved_room_type             <chr> "C", "C", "A", "A", "A", "A", "C", "C",…
## $ assigned_room_type             <chr> "C", "C", "C", "A", "A", "A", "C", "C",…
## $ booking_changes                <dbl> 3, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, …
## $ deposit_type                   <chr> "No Deposit", "No Deposit", "No Deposit…
## $ agent                          <chr> "NULL", "NULL", "NULL", "304", "240", "…
## $ company                        <chr> "NULL", "NULL", "NULL", "NULL", "NULL",…
## $ days_in_waiting_list           <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, …
## $ customer_type                  <chr> "Transient", "Transient", "Transient", …
## $ adr                            <dbl> 0.00, 0.00, 75.00, 75.00, 98.00, 98.00,…
## $ required_car_parking_spaces    <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, …
## $ total_of_special_requests      <dbl> 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 3, …
## $ reservation_status             <chr> "Check-Out", "Check-Out", "Check-Out", …
## $ reservation_status_date        <date> 2015-07-01, 2015-07-01, 2015-07-02, 20…

Arrival Date

arrival_date_year, arrival_date_month, arrival_date_week_numberarrival_date_day_of_month đều chỉ thời gian khách đã đến khách sạn nên sẽ được hợp thành một biến duy nhất arrival_date

Hợp nhất các biến Arrival Date

Hợp nhất các biến trên thành một biến duy nhất arrival_date bằng cách dùng thư viện lubridate

hotels %>%
  #khởi tạo một biến arrival_date với ngày, tháng, năm
  mutate(arrival_date = paste(arrival_date_day_of_month,
                              arrival_date_month,
                              arrival_date_year,
                              sep = "/")) %>%
  #chuyển character về date
  mutate(arrival_date = dmy(arrival_date)) %>%
  
  #loại bỏ các biến sau khi hợp nhất khỏi hotels
  select(-c(arrival_date_year,
            arrival_date_month,
            arrival_date_day_of_month,
            arrival_date_week_number)) -> hotels

Guest Demographics

Trong phần này sẽ xử lý với các biến adults, children, babiescountry. Xóa bỏ các quan sát không có khách và các quan sát với country là NA

#============================ ADULTS VARIABLE ==================================
  #tạo biểu đồ boxplot cho adults
  hotels %>%
    ggplot(aes(x = adults)) +
    geom_boxplot(fill = "#FF7F0E") +
    theme_classic2() +
    labs(x = "Number of Adults", title = "Boxplot") -> adults_boxplot

  #tạo biểu đồ histogram cho adults
  hotels %>%
    ggplot(aes(x = adults)) +
    geom_histogram(fill = "#1F77B4", breaks=seq(0,40,2)) +
    theme_classic2()+
    labs(x = "Number of Adults", title = "Histogram") -> adults_histogram
  
  #kết hợp 2 biểu đồ
  adults_boxplot + adults_histogram -> adults_boxplot_and_histogram



#============================ CHILDREN VARIABLE ================================

  #Có 4 `NA` cho biến `children`, có thể là do lỗi của việc nhập dữ liệu. 
  #Các `NA` này sẽ được coi là `0`
  hotels %>% 
    mutate(children = case_when(is.na(children) ~ 0,
                                TRUE ~ children)
           ) -> hotels
  #Boxplot
  hotels %>% 
    ggplot(aes(x = children)) + 
      geom_boxplot(fill = "#FF7F0E") +
      theme_classic2() +
      labs(x = "Number of Children", title = "Boxplot") -> children_boxplot
  #Histogram
  hotels %>% 
    ggplot(aes(x = children)) + 
      geom_histogram(fill = "#1F77B4", breaks=seq(0,40,2)) +
      theme_classic2() +
      labs(x = "Number of Children", title = "Histogram") -> children_histogram
  
  children_boxplot + children_histogram -> children_boxplot_and_histogram

#=========================== BABIES VARIABLE ===================================

  #boxplot 
  hotels %>% 
    ggplot(aes(x = babies)) + 
      geom_boxplot(fill = "#FF7F0E") +
      theme_classic2() +
      labs(x = "Number of Babies", title = "Boxplot") -> babies_boxplot
  
  #histogram 
  hotels %>% 
    ggplot(aes(x = babies)) + 
      geom_histogram(fill = "#1F77B4", breaks=seq(0,10,0.5)) +
      theme_classic2() +
      labs(x = "Number of Babies", title = "Histogram") -> babies_histogram
  
  babies_boxplot + babies_histogram -> babies_boxplot_and_histogram

#====================== COUNTRY VARIABLE =======================================
  #Đối với biến `country` đầu tiên chúng ta chuyển giá trị `NULL` thành `NA`
  hotels %>% 
  mutate(country = na_if(country, "NULL")) -> hotels
  
  #Sau đó loại bỏ các giá trị `NA` này vì mỗi khách phải có một `country` 
  #nhất định và chuyển `country` về dạng factor
  hotels %>% 
  filter(!is.na(country)) %>% 
  mutate(country = as.factor(country)) -> hotels
#========================== TOTAL GUEST=========================================
  hotels %>% 
    mutate(total_guests = adults + children + babies) -> hotels
#===============================================================================

ggarrange(adults_boxplot_and_histogram, babies_boxplot_and_histogram,
          children_boxplot_and_histogram, nrow = 3, ncol = 1) +
  plot_annotation("Figure 1: Adults, Babies, Children Boxplot and Histogram")

Nhận xét: Biểu đồ boxplot và histogram trong hình 1 trên cho thấy - Hầu hết các quan sát dao động từ 1-3 khách.
Tuy nhiên có một vài trường hợp nhiều hơn, điều này xảy ra có thể do một số lượt đặt phòng cho các đại lý du lịch hoặc công ty lữ hành thực hiện. Do đó,không nên xóa các biến này.
- Hầu hết khách đến khách sạn mà không mang theo trẻ em. Số trẻ em lớn nhất là 10
- Phần lớn khách không mang theo trẻ sơ sinh đến các khách sạn này. Số lượng trẻ sơ sinh lớn nhất được quan sát là 10.
Kết quả này không được coi là outlier vì có thể có một số trường hợp đặc biệt như vậy.
- Có 180 quan sát mà không có khách nào được ghi lại cho một đặt phòng. Chúng ta sẽ loại bỏ nó đi

hotels %>% 
  filter(adults + children + babies >= 1) -> hotels

Reservation Status

Phần này mô tả sự đặt phòng của khách và sẽ làm việc với các biến reservation_status, reservation_status_date, is_canceled, lead_time, days_in_waiting_list.

#==================== IS_CANCELLED VARIABLE ====================================

  #Chuyển `is_canceled` về dạng logic
  hotels %>% 
    mutate(is_canceled = as.logical(is_canceled)) -> hotels

#===================== LEAD_TIME VARIABLE ======================================

  #Boxplot de xac dinh outlier
  hotels %>% 
    ggplot(aes(lead_time)) +
      geom_boxplot(fill = "#FF7F0E") +
      theme_classic2() +
      labs(x = "Lead Time (days)",
           title = "Lead Time Boxplot") -> lead_time_boxplot

#===================== DAY_IN_WAITING_LIST VARIABLE ============================
hotels %>% 
  ggplot(aes(days_in_waiting_list)) +
    geom_boxplot(fill = "#FF7F0E") +
    theme_classic2() +
    labs(x = "Days in Waiting List (days)",
         title = "Days in Waiting List Boxplot") -> days_in_waiting_list_boxplot
#===================== RESERVATION_STATUS VARIABLE =============================

  #Chuyển về dạng factor 
  hotels %>% 
    mutate(reservation_status = as.factor(reservation_status)) -> hotels

#===============================================================================

ggarrange(lead_time_boxplot, days_in_waiting_list_boxplot) +
  plot_annotation("Figure 2: Lead Time and Days in Waiting List Boxplot")

Nhận xét: Dựa vào hình 2 - Chúng ta sẽ loại bỏ các quan sát có lead_time > 625 khỏi tạp dữ liệu.

hotels %>% 
  filter(lead_time <= 625) -> hotels
  • Các days_in_waiting_list > 200 là outlier. Ta sẽ tiến hành loại bỏ
hotels %>% 
  filter(days_in_waiting_list <= 200) -> hotels

User Karma

User Karma chứa các biến số phản ánh hành vi của người dùng đối với khách sạn. Phần này sẽ làm việc với các biến is_repeated_guest, previous_cancellations, và previous_bookings_not_cancelled.

#==================== IS_REPEATED_GUEST VARIABLE ===============================

#Chuyển về dạng logical
hotels %>% 
  mutate(is_repeated_guest = as.logical(is_repeated_guest)) -> hotels

#==================== PREVIOUS_CANCELLATIONS VARIABLE ===========================
hotels %>% 
  ggplot(aes(previous_cancellations)) +
    geom_boxplot(fill = "#FF7F0E") +
    theme_classic2() +
    labs(x = "Previous Cancellations",
         title = "Previous Cancellations") -> previous_cancellations_boxplot

#=================PREVIOUS_BOOKINGS_NOT_CANCELLED VARIABLE =====================
hotels %>% 
  ggplot(aes(previous_bookings_not_canceled)) +
    geom_boxplot(fill = "#FF7F0E") +
    theme_classic2() +
    labs(x = "Previous Bookings not Cancelled",
         title = "Previous Bookings not Cancelled") -> previous_bookings_not_cancelled_boxplot

#===============================================================================
ggarrange(previous_cancellations_boxplot, 
          previous_bookings_not_cancelled_boxplot) +
  plot_annotation("Figure 3: Previous Cancellations and Previous Bookings not Cancelled Boxplot ")

Nhận xét: Dựa theo hình 3 - previous_cancellations > 10 là outlier nhưng sẽ không loại bỏ để tránh phạt các đại lý du lịch và các công ty đặt phòng sẽ phải hủy thay cho khách - Không chỉ rõ một ngưỡng rõ ràng cho các trường hợp outlier của previous_bookings_not_cancelled

Guest Accommodations

Phần này bao gồm các biến mô tả chỗ ở mà mỗi đơn đặt phòng nhận được. Cụ thẻ là stays_in_weekend_nights, stays_in_week_nights, reserved_room_type, assigned_room_type, meals, required_car_parking_spaces, và total_of_special_requests

#===================== STAYS_IN_WEEKEND_NIGHTS VARIABLE ========================
hotels %>% 
  ggplot(aes(stays_in_weekend_nights)) +
    geom_boxplot(fill = "#FF7F0E") +
    theme_classic2() +
    labs(x = "Stays in Weekend Nights",
         title = "Stays in Weekend Nights") -> stays_in_weekend_nights_boxplot
#========================== STAY_IN_WEEK_NIGHTS ================================

hotels %>% 
  ggplot(aes(stays_in_week_nights)) +
    geom_boxplot(fill = "#FF7F0E") +
    theme_classic2() +
    labs(x = "Stays in Week Nights",
         title = "Figure 9: Stays in Week Nights") -> stays_in_week_nights_boxplot

#=============================STAY_NIGHTS VARIABLE =============================

  hotels %>%
  mutate(stays_nights = stays_in_weekend_nights + stays_in_week_nights) -> hotels

#============== `reserved_room_type, assigned_room_type` variables==============

#Đối với các biến này chỉ cần chuyển về dạng factor
hotels %>% 
  mutate(reserved_room_type = as.factor(reserved_room_type)) -> hotels

#=========================== `meals` variable ==================================

#Đối với biến `meals`, chúng ta sẽ chuyển các ký tự viết tắt về dạng 
#đầy đủ sau đó chuyển về dạng factor
hotels %>% 
  mutate(meal = case_when(meal == "Undefined" ~ "No Meal Plan",
                          meal == "SC" ~ "No Meal Plan",
                          meal == "BB" ~ "Bed and Breakfast",
                          meal == "HB" ~ "Half Board",
                          meal == "FB" ~ "Full Board")
         ) %>% 
  mutate(meal = as.factor(meal)) -> hotels

#===============================================================================

ggarrange(stays_in_weekend_nights_boxplot, stays_in_week_nights_boxplot) +
  plot_annotation(" Figure 4: Stays in Weekend Nights and Stays in Week Nights Boxplot")

Nhận xét: Dựa theo hình 4 stays_in_weekend_nightsstays_in_weeks_nights > 5 là outlier nhưng không bị loại bỏ vì có những trường hợp kéo dài thời gian lưu trú

Booking Information

Phần này sẽ làm việc với các biến mô tả các thực thể tham gia vào việc đặt phòng. hotel, market_segment, distribution_channel, booking_changes, deposit_type, agent, company, và customer_type

#======================== `hotel` variable =====================================
    hotels %>% 
      mutate(hotel = as.factor(hotel)) -> hotels

#========================= `market_segment` variable ===========================

  #Loại bỏ các giá trị "Undefined" và chuyển biến này về dạng factor
  hotels %>% 
    filter(market_segment != "Undefined") %>% 
    mutate(market_segment = as.factor(market_segment)) -> hotels

#======================== `distribution_channel` variable ======================

  #Tương tự với `market_segment`, ta cũng loại bỏ "Undefined" và
  #chuyển `distribution_channel` về dạng factor
  hotels %>% 
  filter(distribution_channel != "Undefined") %>% 
  mutate(distribution_channel = as.factor(distribution_channel)) -> hotels

#========================= `agent`, `company` variables ========================
  #Chuyển `NULL` về `NA` sau đó chuyển về kiểu numeric
  hotels %>% 
  mutate(agent = na_if(agent, "NULL")) %>% 
  mutate(agent = as.numeric(agent)) -> hotels

  hotels %>% 
  mutate(company = na_if(company, "NULL")) %>% 
  mutate(company = as.numeric(company)) -> hotels

#========================= `customer_type` variable ============================
  #Chuyển biến này về dạng factor 
  hotels %>%
    mutate(customer_type = as.factor(customer_type)) -> hotels

Average Daily Rate

Doanh thu trung bình hàng ngày được tính bằng cách chia tổng số giao dịch liên quan đến đặt phòng cho tổng số đêm

hotels %>% 
  select(adr) %>% 
  summary
##       adr         
##  Min.   :  -6.38  
##  1st Qu.:  70.00  
##  Median :  95.00  
##  Mean   : 102.20  
##  3rd Qu.: 126.00  
##  Max.   :5400.00

– Ta sẽ loại bỏ giá trị âm

hotels %>% 
  filter(adr >= 0) -> hotels

Data after Preparation

glimpse(hotels)
## Rows: 118,450
## Columns: 31
## $ hotel                          <fct> Resort Hotel, Resort Hotel, Resort Hote…
## $ is_canceled                    <lgl> FALSE, FALSE, FALSE, FALSE, FALSE, FALS…
## $ lead_time                      <dbl> 342, 7, 13, 14, 14, 0, 9, 85, 75, 23, 3…
## $ stays_in_weekend_nights        <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, …
## $ stays_in_week_nights           <dbl> 0, 1, 1, 2, 2, 2, 2, 3, 3, 4, 4, 4, 4, …
## $ adults                         <dbl> 2, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, …
## $ children                       <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, …
## $ babies                         <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, …
## $ meal                           <fct> Bed and Breakfast, Bed and Breakfast, B…
## $ country                        <fct> PRT, GBR, GBR, GBR, GBR, PRT, PRT, PRT,…
## $ market_segment                 <fct> Direct, Direct, Corporate, Online TA, O…
## $ distribution_channel           <fct> Direct, Direct, Corporate, TA/TO, TA/TO…
## $ is_repeated_guest              <lgl> FALSE, FALSE, FALSE, FALSE, FALSE, FALS…
## $ previous_cancellations         <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, …
## $ previous_bookings_not_canceled <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, …
## $ reserved_room_type             <fct> C, A, A, A, A, C, C, A, D, E, D, D, G, …
## $ assigned_room_type             <chr> "C", "C", "A", "A", "A", "C", "C", "A",…
## $ booking_changes                <dbl> 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, …
## $ deposit_type                   <chr> "No Deposit", "No Deposit", "No Deposit…
## $ agent                          <dbl> NA, NA, 304, 240, 240, NA, 303, 240, 15…
## $ company                        <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
## $ days_in_waiting_list           <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, …
## $ customer_type                  <fct> Transient, Transient, Transient, Transi…
## $ adr                            <dbl> 0.00, 75.00, 75.00, 98.00, 98.00, 107.0…
## $ required_car_parking_spaces    <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, …
## $ total_of_special_requests      <dbl> 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 3, 1, …
## $ reservation_status             <fct> Check-Out, Check-Out, Check-Out, Check-…
## $ reservation_status_date        <date> 2015-07-01, 2015-07-02, 2015-07-02, 20…
## $ arrival_date                   <date> 2015-07-01, 2015-07-01, 2015-07-01, 20…
## $ total_guests                   <dbl> 2, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, …
## $ stays_nights                   <dbl> 0, 1, 1, 2, 2, 2, 2, 3, 3, 4, 4, 4, 4, …

EXPLORATORY DATA ANALYSIS

Analysis of Bookings

Bookings by Country

Bản đồ thế giới là cách tốt nhất để hình dung các quốc gia xuất xứ của khách đặt phòng. Để tạo bản đồ thế giới, tên quốc gia được ánh xạ tới mã country được liên kết với mỗi lượt đặt phòng. Tên country đa số được giữ nguyên, tuy nhiên, trong một số trường hợp, tên quốc gia trong bản đồ thế giới của map_data không khớp với bộ dữ liệu ISOCodes, do vậy, ta sẽ cần định nghĩa lại cho một số quốc gia. Ngoài ra, đặt trước từ các quốc gia nằm dưới mức độ chi tiết của dữ liệu bản đồ thế giới được phân bổ cho quốc gia kiểm soát hoặc nước láng giềng gần nhất. Ví dụ: đặt chỗ từ Hong Kong được phân bổ cho China.

# Load the world map data
world_map = map_data(map = "world")

hotels %>% 
  # Thêm full name cho mỗi quốc gia
  left_join(y = ISO_3166_1, by = c("country" = "Alpha_3")) %>% 
  # Sửa lại tên để khớp với các vùng trong world map 
  mutate(Name = case_when(Name == "French Southern Territories" ~ "French Southern and Antarctic Lands",
                          Name == "Bolivia, Plurinational State of" ~ "Bolivia",
                          Name == "Côte d'Ivoire" ~ "Ivory Coast",
                          Name == "CN" ~ "China",
                          Name == "Cabo Verde" ~ "Cape Verde",
                          Name == "Czechia" ~ "Czech Republic",
                          Name == "United Kingdom" ~ "UK",
                          # Không có Gibraltar nên sẽ nhập vào Spain
                          Name == "Gibraltar" ~ "Spain",
                          # Hong Kong sẽ được add vào China
                          Name == "Hong Kong" ~ "China",
                          Name == "Iran, Islamic Republic of" ~ "Iran",
                          Name == "Saint Kitts and Nevis" ~ "Saint Kitts",
                          Name == "Korea, Republic of" ~ "South Korea",
                          Name == "Lao People's Democratic Republic" ~ "Laos",
                          # Macao sẽ được add vào China
                          Name == "Macao" ~ "China",
                          Name == "Russian Federation" ~ "Russia",
                          Name == "Syrian Arab Republic" ~ "Syria",
                          Name == "Taiwan, Province of China" ~ "Taiwan",
                          country == "TMP" ~ "Timor-Leste",
                          Name == "Tanzania, United Republic of" ~ "Tanzania",
                          Name == "United States Minor Outlying Islands" ~ "USA",
                          Name == "United States" ~ "USA",
                          Name == "Venezuela, Bolivarian Republic of" ~ "Venezuela",
                          Name == "Virgin Islands, British" ~ "Virgin Islands",
                          Name == "Viet Nam" ~ "Vietnam",
                          country == "CN" ~ "China",
                          TRUE ~ Name)
         ) %>% 
  rename(country_name = Name, country_abbr = country) %>% 
  # Bỏ các biến thừa
  select(-Alpha_2, -Numeric, -Official_name, -Common_name) -> hotels

# Tạo boxplot
hotels %>% 
  # Dếm số lượng đặt phòng theo quốc gia 
  count(country_name) %>% 
  # Tạo world m
  ggplot() +
    # Thêm một layer để hiển thị tất cả các quốc gia ngay cả các quốc gia không có dữ liệu 
    geom_map(data = world_map,
             map = world_map,
             aes(map_id = region),
             fill = "white",
             color = "black"
             ) +
    # Tô màu map dựa trên số lượng đặt phòng 
    geom_map(aes(map_id = country_name, fill = n), map = world_map) +
      expand_limits(x = world_map$long, y = world_map$lat) +
      scale_fill_continuous(name = "", type = "viridis") +
      labs(title = "Figure 5: World Map of Bookings",
           x = "Longitude (°)",
           y = "Lattitude (°)") 

Nhận xét: Hình 5 cho thấy tỷ lệ đặt phòng lớn nhất đến từ Bồ Đào Nha và Châu Âu. Những khách sạn Bồ Đào Nha này đã nhận được đặt phòng từ mọi châu lục. Tuy nhiên, Nam Cực được bao gồm do nó được bao gồm trong Lãnh thổ phía Nam của Pháp chứ không phải khách đặt phòng từ Nam Cực.

Bookings by Hotel Type

Bây giờ chúng ta sẽ xét đến số lượng đặt phòng liên quan đến mỗi khách sạn. Hình 6 bên dưới so sánh số lượng đặt phòng cho City Hotel và Resort Hotel từ năm 2015 đến năm 2017. Sau đó, Hình 7 và hình 8 so sánh số lượng đặt phòng theo cả năm và tháng đến.

hotels %>% 
  mutate(arrival_year = year(arrival_date)) %>% 
  group_by(arrival_year) %>%
  ggplot() +
    geom_bar(aes(x = hotel, fill = hotel)) + 
    scale_fill_manual(values=c("#1F77B4","#FF7F0E"))+
    theme_classic2() +
    facet_wrap(~ arrival_year, nrow = 1) +
    labs(x = "",
         y = "Number of Bookings",
         fill = "Hotel",
         title = "Figure 6: Hotel Bookings by Year") -> hotel_bookings_by_year
ggplotly(hotel_bookings_by_year)
hotels %>% 
  mutate(Month = month(arrival_date, label = TRUE, abbr = TRUE), Hotel = hotel) %>% 
  ggplot() +
    geom_bar(aes(x = Month, fill = Hotel),
             position = "dodge") + 
    scale_fill_manual(values=c("#1F77B4","#FF7F0E"))+
    theme_classic() +
    facet_wrap(~ year(arrival_date), nrow = 3) +
    labs(x = "",
         y = "Number of Bookings",
         fill = "Hotel",
         title = "Figure 7: Hotel Bookings by Month") -> hotel_bookings_by_month
ggplotly(hotel_bookings_by_month)
hotels %>%
  mutate(Month = month(arrival_date, label = TRUE), Year = year(arrival_date) ) %>%
  select(Month, Year) %>%
  mutate(count = 1) %>%
  group_by(Year,Month) %>%
  summarise(count = sum(count)) -> hotel_bookings_per_year
colnames(hotel_bookings_per_year) <- c("Year","Month", "Total")
hotel_bookings_per_year <- hotel_bookings_per_year %>% 
  mutate(label=glue("Year: {Year}
                    Month: {Month}
                    Number of Bookings: {comma(Total)}"))  
bookings_plot <- ggplot(data = hotel_bookings_per_year, aes( y=Total,
                              x = Year, text = label)) +
  geom_col(aes(fill = Month)) +
  labs(title = "Figure 8: Number of Booking Per Year",
       x = "Total Bookings",
       y = "Year",
       fill = "Month") +
  theme_minimal()
ggplotly(bookings_plot, tooltip = "text")

Nhận xét: Những con số này cho thấy City Hotel có nhiều đặt phòng hơn khách sạn Resort hàng năm. Năm 2016 chứng kiến số lượng đặt phòng tối đa cho cả hai khách sạn. Tuy nhiên, năm 2016 đã có nhiều lượt đặt trước hơn vì năm 2015 và 2017 chúng ta không có dữ liệu cho cả năm mà chỉ có các tháng cụ thể.

Bookings by Distributiion Channel

Xem xét số lượng đặt phòng theo kênh phân phối, biết được kênh phân phối nào hiệu quả nhất, khách sạn sẽ tập trung vào kênh đó để thúc đẩy đặt phòng

hotels %>% 
  ggplot(aes(distribution_channel, fill = hotel)) +
    geom_bar(position = 'dodge') +
    scale_y_continuous(name = "Number of Bookings",labels = scales::comma) +
    scale_fill_manual(values=c("#1F77B4","#FF7F0E"))+
    theme_classic2() +
    xlab("Distribution Channel") +
    ggtitle("Figure 9: Bookings by Distribution Channel") +
    labs(fill = 'Hotel') -> bookings_by_distribution_channel
    
ggplotly(bookings_by_distribution_channel)

Nhận xét: Theo Hình 9, các đại lý du lịch và công ty lữ hành là những kênh lớn nhất về lượng đặt phòng cho cả hai khách sạn. Điều này là hợp lý, vì các thực thể này có một lượng khách du lịch liên tục đến khách sạn. City Hotel có nhiều đặt phòng trực tiếp hơn Resort Hotel.

Analysis of Cancellations

Đầu tiên, chúng ta so sánh số lượng đặt phòng bị hủy và không bị hủy. Điều này bao gồm so sánh giữa các khách sạn và năm. Sau đó, chúng ta so sánh số lượng đặt phòng bị hủy giữa các kênh phân phối. Cuối cùng, chúng tôi so sánh tỷ lệ đặt phòng bị hủy từ mỗi quốc gia.

Bookings vs Cancellations

Hình 10 so sánh tổng số lượt đặt trước với số lượt hủy trên toàn bộ tập dữ liệu. Những con số này được chia nhỏ theo năm trong Hình 11.

hotels %>% 
  ggplot() +
    geom_bar(aes(x = is_canceled, fill = hotel),
             position = "dodge") +
    scale_fill_manual(values=c("#1F77B4","#FF7F0E"))+
    theme_minimal() +
    labs(x = "Cancelled",
         y = "Number of Bookings",
         fill = "Hotel",
         title = "Figure 10: Total Booking Cancellations by Hotel") -> bookings_cancellations_by_hotel
ggplotly(bookings_cancellations_by_hotel)
hotels %>% 
  mutate(arrival_year = year(arrival_date)) %>% 
  ggplot() +
    geom_bar(aes(x = is_canceled, fill = hotel),
             position = "dodge") +
    facet_wrap(~ arrival_year, nrow = 1) +
    scale_fill_manual(values=c("#1F77B4","#FF7F0E"))+
    theme_classic2() +
    labs(x = "Cancelled",
         y = "Number of Bookings",
         fill = "Hotel",
         title = "Figure 11: Booking Cancellations by Hotel and Year") -> bookings_cancellations_by_hotel_year
ggplotly(bookings_cancellations_by_hotel_year)

Nhận xét: Hình 10 và 11 đều chỉ ra rằng số lượng đặt phòng lớn hơn số lượng hủy bỏ của cả hai khách sạn. Do dữ liệu không đầy đủ trong năm 2015 và 2017, năm 2016 có nhiều đặt phòng và hủy hơn so với cả hai năm này. City Hotel có nhiều lượt hủy hơn, nhưng cũng có nhiều đặt phòng hơn. Để giảm thiểu những vấn đề này, Bảng 19 dưới đây cho thấy tỷ lệ đặt phòng bị hủy và không bị hủy theo năm.

hotels %>% 
  
  mutate(arrival_year = year(arrival_date)) %>% 
  group_by(hotel, arrival_year) %>% 
  count(is_canceled) %>% 
  
  group_by(hotel, arrival_year) %>% 
  summarise(is_canceled = is_canceled,
            percentages = round(100 * n / sum(n), 2)
            ) %>%
  
  pivot_wider(names_from = arrival_year, values_from = percentages) %>% 
  kable(format = "pipe",
        col.names = c("Hotel",
                      "Cancellation Status",
                      "2015 (%)",
                      "2016 (%)",
                      "2017 (%)"),
        caption = "Table 4: Hotel Cancellation and Year Contingency Table")
Table 4: Hotel Cancellation and Year Contingency Table
Hotel Cancellation Status 2015 (%) 2016 (%) 2017 (%)
City Hotel FALSE 56.12 59.64 57.48
City Hotel TRUE 43.88 40.36 42.52
Resort Hotel FALSE 74.11 73.17 69.09
Resort Hotel TRUE 25.89 26.83 30.91

Nhận xét: 37,13% lượng đặt phòng bị hủy ở cả hai loại khách sạn. Bảng 4 cho thấy tỷ lệ hủy đặt phòng đối với City Hotel cao hơn so với Resort. Có thể giải thích cho điều này là số lượng hủy phòng nhiều hơn của City Hotel là do khách sạn này có nhiều đặt phòng của công ty hơn so với Resort City. Nghiên cứu sâu hơn nên được thực hiện để xác định nguyên nhân của việc hủy thêm và khắc phục những vấn đề đó.

Analysis of No-Shows vs. Formal Cancellations

Phần này sẽ so sánh tỷ lệ khách hủy bỏ do không đến nhận phòng (“No-show”) với tỷ lệ khách hủy bỏ chính đáng (“Cancelled”). Đây là thông tin hữu ích vì nó cho biết khả năng khách sạn hủy đặt phòng là bao nhiêu

hotels %>% 
  filter(is_canceled == TRUE) %>% 
  group_by(reservation_status) %>% 
  count(is_canceled) %>% 

  ungroup() %>% 
  mutate(percentage = round(100 * n / sum(n), 2)) %>% 
  
  select(-is_canceled) %>% 
  kable(format = "pipe",
        col.names = c("Reservation Status",
                      "Number of Bookings",
                      "Percentage of Cancellations"),
        caption = "Table 5: Cancellation Contingency Table")
Table 5: Cancellation Contingency Table
Reservation Status Number of Bookings Percentage of Cancellations
Canceled 42779 97.27
No-Show 1202 2.73

Nhận xét: Trong số 44.142 lượt hủy, 42.940 (~97%) là hủy chính đáng và 1202 lượt còn lại là không đến nhận phòng. Điều này có nghĩa là khách có khả năng thông báo trước cho khách sạn của họ trước khi hủy. Thông báo trước làm cho nhiều khả năng đăng ký có thể được chỉ định lại. Bảng 6 và Hình 12 so sánh các kết quả này giữa City Hotel và Resort Hotel.

hotels %>% 
  filter(is_canceled == TRUE) %>% 
  mutate(arrival_year = year(arrival_date)) %>% 
  ggplot(aes(x = reservation_status, fill = hotel)) + 
    geom_bar(position = "dodge") +
    scale_fill_manual(values=c("#1F77B4","#FF7F0E"))+
    facet_grid(cols = vars(arrival_year)) +
    theme_classic2() +
    labs(x = "Cause",
         y = "Number of Bookings",
         fill = "Hotel",
         title = "Figure 12: Cancellations by Hotel, Year, and Cause") -> cancellations_by_hotel_cause
ggplotly(cancellations_by_hotel_cause)
hotels %>% 
  filter(is_canceled == TRUE) %>% 
  
  group_by(hotel, reservation_status) %>% 
  count(is_canceled) %>% 
  
  ungroup() %>% 
  mutate(percentage = round(100 * n / sum(n), 2)) %>% 
  
  select(-is_canceled) %>% 
  kable(format = "pipe",
        col.names = c("Hotel",
                      "Reservation Status",
                      "Number of Bookings",
                      "Percentage of Cancellations"),
        caption = "Table 6: Cancellations by Hotel and Cause")
Table 6: Cancellations by Hotel and Cause
Hotel Reservation Status Number of Bookings Percentage of Cancellations
City Hotel Canceled 31989 72.73
City Hotel No-Show 915 2.08
Resort Hotel Canceled 10790 24.53
Resort Hotel No-Show 287 0.65

Nhận xét: Trong tổng số lượt hủy, khoảng 72,3% là đối với City Hotel và 24,5% đối với Khách sạn Resort. Có khoảng 2% không đến nhận phòng đối với các City Hotelvà dưới 1% đối với các khách sạn Resort. Một lần nữa, số lượng hủy bỏ nhiều hơn đã được quan sát thấy trong năm 2016 do nửa năm đại diện cho năm 2015 và 2017.

Cancellations by Distribution Channel

Trong phần này, việc hủy đặt phòng được đánh giá theo kênh phân phối. Số lần hủy theo kênh phân phối được thống kê theo khách sạn về tổng số lần hủy (Bảng 7) và tỷ lệ phần trăm số lần hủy của từng khách sạn (Bảng 8). Bảng 9 cho thấy tỷ lệ hủy phòng do từng kênh phân phối khi xem xét cả hai khách sạn.

hotels %>% 
  group_by(hotel, distribution_channel) %>% 
  summarise(total_cancellations = sum(is_canceled, na.rm = TRUE)) %>% 
  pivot_wider(names_from = distribution_channel,
              values_from = total_cancellations) %>% 
  kable(format = "pipe",
        caption = "Table 7: Cancellations by Hotel and Distribution Channel")
Table 7: Cancellations by Hotel and Distribution Channel
hotel Corporate Direct GDS TA/TO
City Hotel 779 1230 37 30858
Resort Hotel 675 1312 NA 9090
hotels %>% 
  filter(is_canceled == TRUE) %>% 
  group_by(hotel, distribution_channel) %>% 
  count(is_canceled) %>% 
  # Tính phần trăm lượt hủy theo kênh phân phối
  group_by(hotel) %>% 
  mutate(percentage = round(100 * n / sum(n), 2)) %>% 
  # Xóa bỏ biến không cần thiết
  select(-c(is_canceled, n)) %>% 
  
  pivot_wider(names_from = distribution_channel, values_from = percentage) %>% 
  kable(format = "pipe",
        caption = "Table 8: Percentage of Hotel Cancellations by Distribution Channel")
Table 8: Percentage of Hotel Cancellations by Distribution Channel
hotel Corporate Direct GDS TA/TO
City Hotel 2.37 3.74 0.11 93.78
Resort Hotel 6.09 11.84 NA 82.06
hotels %>% 
  filter(is_canceled == TRUE) %>% 
  group_by(distribution_channel) %>% 
  count(is_canceled) %>% 
  
  ungroup() %>% 
  mutate(percentage = round(100 * n / sum(n), 2)) %>% 
  
  select(-is_canceled) %>% 
  kable(format = "pipe",
        col.names = c("Distribution Channel",
                      "Cancellations",
                      "Percentage of Cancellations"),
        caption = "Table 9: Percentage of Cancellations by Distribution Channel")
Table 9: Percentage of Cancellations by Distribution Channel
Distribution Channel Cancellations Percentage of Cancellations
Corporate 1454 3.31
Direct 2542 5.78
GDS 37 0.08
TA/TO 39948 90.83

Nhận xét: Từ Bảng 8, chúng ta có thể thấy rằng hơn 80% số lượt hủy bỏ phòng đến từ kênh phân phối của đại lý du lịch và công ty lữ hành. Direct là nguồn hủy bỏ phổ biến thứ hai. Điều thú vị là khách sạn Resort có nhiều lượt hủy bỏ từ các kênh phân phối Doanh nghiệp và Direct hơn nhiều so với khách sạn City, nhưng tỷ lệ hủy bỏ từ các đại lý du lịch và công ty lữ hành thấp hơn. Theo Bảng 9, hơn 90% số lần hủy phòng đến từ các đại lý du lịch và công ty lữ hành.

Hình 13 cho biết tỷ lệ đặt phòng bị hủy đối với từng kênh phân phối, theo khách sạn.

hotels %>%
  
  group_by(hotel, distribution_channel) %>% 
  summarise(n_canceled = sum(is_canceled),
            n_total = n(), percentage = 100 * n_canceled / n_total) %>% 
  
  ggplot() +
    geom_col(aes(x = distribution_channel,
                 y = percentage,
                 fill = hotel),
             position = "dodge") +
    scale_fill_manual(values=c("#1F77B4","#FF7F0E"))+
    theme_minimal() +
    labs(x = "Distribution Channel",
         y = "Percentage of Bookings Cancelled",
         title = "Figure 13: Percentage of Bookings Cancelled by Distribution Channel",
         fill = "Hotel") -> percentage_bookings_cancelled_distribution
ggplotly(percentage_bookings_cancelled_distribution)

Nhận xét: Đối với cả 2 khách sạn, đại lý du lịch và công ty lữ hành có tỷ lệ hủy đặt phòng cao nhất. Trong khi các đại lý du lịch và công ty lữ hành chiếm hơn 90% số lần hủy, họ chỉ có tỷ lệ hủy dưới 50% đối với cả 2 khách sạn. Điều này cho thấy rằng các đại lý du lịch và công ty lữ hành chiếm tỷ lệ cao trong số các trường hợp hủy do có một lượng lớn đặt phòng bị hủy.

Cancellations by Country

Phần này sẽ tìm quốc gia có tỷ lệ hủy bỏ đặt phòng cao nhất. Điều này được đánh giá bằng cách tìm tỷ lệ đặt phòng từ mỗi quốc gia dẫn đến hủy bỏ. Sử dụng dữ liệu từ mọi khoảng thời gian và cả 2 khách sạn

hotels %>% 
  group_by(country_name) %>% 
  
  summarise(n_cancelled = sum(is_canceled),
            n_total = n(),
            percentage_cancellations = round(100 * n_cancelled / n_total,
                                             2)
            ) %>% 
  slice_max(n_cancelled, n = 10) %>% 
  
  select(country_name,
         n_cancelled,
         n_total,
         percentage_cancellations) %>% 
  kable(format = "pipe",
        col.names = c("Country",
                      "Cancelled Bookings",
                      "Total of Bookings",
                      "Percentage of Cancellations"),
        caption = "Table 7: Cancellations by Country")
Table 7: Cancellations by Country
Country Cancelled Bookings Total of Bookings Percentage of Cancellations
Portugal 27345 48297 56.62
UK 2452 12118 20.23
Spain 2188 8577 25.51
France 1933 10341 18.69
Italy 1333 3761 35.44
Germany 1218 7261 16.77
Ireland 832 3374 24.66
Brazil 830 2222 37.35
China 757 2323 32.59
USA 502 2094 23.97

Nhận xét: Bảng 7 cho thấy danh sách 10 quốc gia hàng đầu có số lần hủy đặt phòng cao nhất. Tỷ lệ hủy không được sử dụng để xếp hạng vì tất cả các quốc gia trong danh sách đó đều có tỷ lệ hủy là 100%. Đây là một vấn đề vì tất cả các quốc gia được liệt kê đều có một số lượng nhỏ các trường hợp hủy, nhưng tất cả các đặt phòng của họ đều bị hủy. Hiển thị tỷ lệ hủy cho các quốc gia có số lượng hủy lớn nhất được coi là nhiều thông tin hơn. Hình 14 thể hiện tỷ lệ hủy theo tỷ lệ phần trăm cho mỗi quốc gia.

hotels %>%
  group_by(country_name) %>%
  
  summarise(n_cancelled = sum(is_canceled),
            n_total = n(),
            percentage_cancellations = 100 * n_cancelled / n_total) %>% 
  
  ggplot() +
    
    geom_map(data = world_map,
             map = world_map,
             aes(map_id = region),
             fill = "white",
             color = "black"
             ) +
    
    geom_map(aes(map_id = country_name,
                 fill = percentage_cancellations),
             map = world_map) +
      expand_limits(x = world_map$long, y = world_map$lat) +
      scale_fill_continuous(name = "", type = "viridis") +
      labs(title = "Figure 14: Percentage of Bookings Cancelled World Map",
           x = "Longitude (°)",
           y = "Lattitude (°)"
           )

Nhận xét: Nam Cực có dữ liệu do Lãnh thổ phía Nam của Pháp bao gồm cả Nam Cực. Bồ Đào Nha là quốc gia có tỷ lệ hủy bỏ cao nhất trong danh sách top 10, với tỷ lệ hủy bỏ là 56,6%. Điều thú vị là quốc gia sở tại của khách sạn sẽ có số lượng hủy đặt phòng lớn hơn so với các quốc gia còn lại. Điều này có thể cho thấy rằng khách hàng Bồ Đào Nha có nhiều khả năng đặt phòng tại các khách sạn địa phương hơn khi kế hoạch của họ không chắc chắn. Và sau đó sẵn sàng hủy bỏ hơn khi kế hoạch của họ thay đổi vì họ không phải sắp xếp chuyến du lịch quốc tế để đến khách sạn của mình.

Tỷ lệ hủy bỏ dường như cao hơn bên ngoài châu Âu. Các quốc gia ở Nam bán cầu cũng có nhiều khả năng hủy bỏ. Điều này có thể là do khoảng cách đến các quốc gia này. Bất kỳ sự gián đoạn nào đối với việc sắp xếp việc đi lại cũng sẽ trở nên khó khăn hơn đối với những khách sống bên ngoài Khu vực Shengen.

Analysis of Repeated Guests

Dùng biểu đồ time series để đánh giá tỷ lệ đặt phòng của khách cũ .Tỷ lệ đặt phòng được sử dụng sao cho chuỗi thời gian không bị ảnh hưởng bởi một phần dữ liệu trong năm 2015 và 2017. Hình 15 cho thấy tỷ lệ đặt phòng do khách cũ thực hiện có tính đến cả hai khách sạn.

hotels %>% 
  # số lượng đặt phòng bởi khách cũ 
  group_by(arrival_date) %>%
  count(is_repeated_guest) %>% 
  # Tính phần trăm booking by repeated và new guest
  mutate(percentage = 100 * n / sum(n)) %>% 
  filter(is_repeated_guest == TRUE) %>% 
  ggplot() +
    geom_line(aes(x = arrival_date,
                  y = percentage), color = "#FF7F0E"
              ) +
  theme_minimal() +
    labs(x = "Arrival Date",
         y = "Percentage of Repeat Guests",
         title = "Figure 15: Repeat Guests Time-Series") -> repeat_guest_time_series
ggplotly(repeat_guest_time_series)

Nhận xét: Hơn một nửa số lượt đặt phòng được thực hiện bởi khách mới.Có vẻ như tỷ lệ khách lặp lại tăng trong những tháng mùa đông và thấp hơn trong những tháng mùa hè.

hotels %>% 
  group_by(arrival_date, hotel) %>%
  summarise(n_repeated_guests = sum(is_repeated_guest),
            n_total = n(),
            percent_of_bookings = round(100 * n_repeated_guests / n_total,
                                        2)
            ) %>% 
  filter(n_repeated_guests > 0) %>% 
  ggplot() +
    geom_line(aes(x = arrival_date,
                  y = percent_of_bookings,
                  colour = hotel)
              ) +
  scale_color_manual(values=c("#1F77B4","#FF7F0E"))+
  theme_minimal() +
    facet_wrap(~ hotel, ncol = 2) +
    labs(x = "Arrival Date",
         y = "Percentage of Repeat Guests",
         colour = "",
         title = "Figure 16: Repeat Guests Time-Series by Hotel") -> repeat_guest_time_series_by_hotel
ggplotly(repeat_guest_time_series_by_hotel)

Analysis of Average Daily Rate

adr trung bình trong mỗi tháng được sử dụng để đánh giá mức độ thay đổi của số tiền mà khách sẵn sàng chi tiêu tại khách sạn trong suốt cả năm. Hình 21 bên dưới hiển thị chuỗi thời gian cho dữ liệu .

hotels %>% 
  filter(is_canceled == FALSE) %>% 
  mutate(arrival_year = year(arrival_date),
         arrival_month = month(arrival_date),
         arrival_time = ym(paste(arrival_year, arrival_month))
         ) %>% 
  select(-arrival_year, -arrival_month) %>% 
  group_by(arrival_time) %>% 
  summarise(monthly_mean_adr = mean(adr)) -> monthly_adr


monthly_adr %>% 
  ggplot() +
    geom_line(aes(x = arrival_time, y = monthly_mean_adr), color = "#FF7F0E") +
    theme_minimal() +
    labs(x = "Month",
         y = "Monthly Average of ADR (€)",
         title = "Figure 17: Monthly ADR Time-Series") -> monthly_adr_time_series
ggplotly(monthly_adr_time_series)

Nhận xét: Có vẻ như khách sạn sẵn sàng chi tiền nhiều hơn cho những tháng mùa hè sao với những tháng mùa đông . Dự báo mức trung bình hàng tháng sẽ là cơ sở tốt nhất để định giá. Mô hình ARIMA sẽ được sử dụng cho dự báo này.

monthly_adr %>%
  select(monthly_mean_adr) %>% 
  as.ts() %>% 

  auto.arima() -> monthly_adr_model


monthly_adr_model %>% 
  forecast(h = 24) %>%
  as_tibble() %>% 

  mutate(forecast_month_index = 1:n(),
         forecast_month = add_with_rollback(ym("2017-08"),
                                               months(forecast_month_index),
                                            roll_to_first = TRUE)
         ) %>% 
  ggplot() +

    geom_line(data = monthly_adr, aes(x = arrival_time,
                                      y = monthly_mean_adr),
              color = "#1F77B4") +
    theme_minimal() +

    geom_line(aes(x = forecast_month, y = `Point Forecast`), color = "#FF7F0E") +
    labs(x = "Year",
         y = "Monthly Average ADR (€)",
         title = "Figure 18: Monthly ADR Forecast")

Nhận xét: Dự đoán trong hình 18 cho thấy adr trung bình hàng tháng sẽ giảm khi mùa hè kết thúc. Nhưng nó dự báo năm tới adr trung bình hàng tháng đạt đến một mức tiệm cận. Kết quả này không phù hợp với dữ liệu.

Analysis of Revenue

hotels %>% 
  mutate(total_stay = stays_in_weekend_nights + stays_in_week_nights,
         revenue = adr * total_stay) -> hotels

#Total revenue by hotel type and year
hotels %>% 
  filter(is_canceled == FALSE) %>% 
  mutate(arrival_year = year(arrival_date)) %>% 
  group_by(hotel, arrival_year) %>% 
  summarise(total_revenue = sum(revenue)) %>% 
  pivot_wider(names_from = arrival_year, values_from = total_revenue) %>% 
  kable(format = "pipe",
        caption = "Table 8: Total Hotel Revenue (€)")
Table 8: Total Hotel Revenue (€)
hotel 2015 2016 2017
City Hotel 1894096 6829689 5635269
Resort Hotel 2583431 4761723 4167347

Nhận xét: Trong mọi năm trừ 2015, City Hotel kiếm được nhiều tiền hơn so với khách sạn Resort. Trong nửa đầu năm 2017, doanh thu khoảng 5,6 triệu euro đối với City Hotel và 4,1 triệu euro đối với Resort Hotel. Giả sử trong nửa cuối năm 2017 có hiệu suất giống như 2015 thì tổng doanh thu năm 2017 sẽ cao hơn bất kỳ năm nào khác.

Tổng doanh thu từ các lượt đặt phòng bị hủy bỏ thể hiện trong bảng 27 sau:

hotels %>% 
  filter(reservation_status != "Check-Out") %>% 
  group_by(reservation_status, deposit_type) %>% 
  summarise(total_revenue = sum(revenue)) %>% 
  pivot_wider(names_from = deposit_type, values_from = total_revenue) %>% 
  kable(format = "pipe",
        caption = "Table 9: Revenue from Cancelled and No-Show Bookings (€)")
Table 9: Revenue from Cancelled and No-Show Bookings (€)
reservation_status No Deposit Non Refund Refundable
Canceled 12633147 3580656 19950.17
No-Show 441359 7471 48.00

Nhận xét: Các lượt đặt phòng bị hủy bỏ tạo ra doanh thu gấp nhiều lần so với các trường hợp hủy bỏ do không đến nhận phòng. Việc tạo ra doanh thu không hoàn lại và có có hoàn lại sẽ hợp lý hơn vì khách sạn đang kiếm doanh thu thông qua tiền đặt cọc của khách.

Analysis of Revenue by Country

hotels %>% 
  group_by(country_name) %>% 
  summarise(mean_revenue = mean(revenue)) %>% 
  
  ggplot() +
   
    geom_map(data = world_map,
             map = world_map,
             aes(map_id = region),
             fill = "white",
             color = "black"
             ) +
    
    geom_map(aes(map_id = country_name,
                 fill = mean_revenue),
             map = world_map) +
      expand_limits(x = world_map$long, y = world_map$lat) +
      scale_fill_continuous(name = "", type = "viridis") +
      labs(title = "Figure 20: Average Revenue per Booking World Map",
           x = "Longitude (°)",
           y = "Lattitude (°)"
           )  

Nhận xét: Hầu hết các quốc gia có trung bình khoảng 500 euro trên mỗi lần đặt phòng. Ả-rập Xê-út, Ai Cập, Ghana và Ăng-gô-la đều là ví dụ về các quốc gia tạo ra doanh thu đặt phòng cao. Madagascar là một ví dụ về quốc gia có doanh thu trung bình trên mỗi lần đặt phòng thấp.

LOGISTIC REGRESSION

Traning and Testing Sets

Dữ liệu sẽ được chia ngẫu nhiên 80% training và 20% testing

index <- createDataPartition(hotels$is_canceled, p=0.80, list=FALSE)
# Chọn 20% dữ liệu cho testing
test <- hotels[-index,]
# 80% dữ liệu cho training
train <- hotels[index,]

Training Logistic Regresstion Model

Chúng ta sẽ bắt đầu với mô hình đầy đủ dự đoán hủy đặt phòng dựa trên loại khách sạn (hotel), số ngày từ khi đặt phòng đến lúc nhận phòng (lead_time), trung bình hàng ngày (adr), loại thức ăn (meal), kênh phân phối (distribution_channel),…

fullmodel <- glm(is_canceled ~ hotel + lead_time  + adr +  stays_nights + total_guests + 
                  meal + total_of_special_requests + distribution_channel +
                 is_repeated_guest + previous_cancellations + booking_changes +
                 deposit_type + days_in_waiting_list + required_car_parking_spaces +
                 customer_type, family = "binomial" , data=train)

Tìm mô hình tốt nhất bằng cách sử dụng backward elimination

model_step_b <- step(fullmodel,direction='backward')
## Start:  AIC=85178.08
## is_canceled ~ hotel + lead_time + adr + stays_nights + total_guests + 
##     meal + total_of_special_requests + distribution_channel + 
##     is_repeated_guest + previous_cancellations + booking_changes + 
##     deposit_type + days_in_waiting_list + required_car_parking_spaces + 
##     customer_type
## 
##                               Df Deviance   AIC
## <none>                              85132 85178
## - hotel                        1    85143 85187
## - days_in_waiting_list         1    85146 85190
## - total_guests                 1    85213 85257
## - stays_nights                 1    85230 85274
## - meal                         3    85460 85500
## - is_repeated_guest            1    85460 85504
## - booking_changes              1    85700 85744
## - distribution_channel         3    86024 86064
## - adr                          1    86104 86148
## - lead_time                    1    86595 86639
## - customer_type                3    86675 86715
## - previous_cancellations       1    87086 87130
## - total_of_special_requests    1    87959 88003
## - required_car_parking_spaces  1    88561 88605
## - deposit_type                 2    94254 94296

Giá trị AIC hoặc BOC thấp cho thấy mức độ phù hợp tốt hơn. Loại bỏ hotelday_in_waiting_list

finalmodel <- glm(is_canceled ~  lead_time  + adr +  stays_nights + total_guests + 
                    meal + total_of_special_requests + distribution_channel +
                    is_repeated_guest + previous_cancellations + booking_changes +
                    deposit_type + required_car_parking_spaces +
                    customer_type, family = "binomial" , data=train)
summary(finalmodel)
## 
## Call:
## glm(formula = is_canceled ~ lead_time + adr + stays_nights + 
##     total_guests + meal + total_of_special_requests + distribution_channel + 
##     is_repeated_guest + previous_cancellations + booking_changes + 
##     deposit_type + required_car_parking_spaces + customer_type, 
##     family = "binomial", data = train)
## 
## Deviance Residuals: 
##     Min       1Q   Median       3Q      Max  
## -6.7118  -0.7883  -0.4617   0.2494   3.6033  
## 
## Coefficients:
##                                Estimate Std. Error z value Pr(>|z|)    
## (Intercept)                  -3.036e+00  7.467e-02 -40.665  < 2e-16 ***
## lead_time                     3.739e-03  9.828e-05  38.042  < 2e-16 ***
## adr                           6.263e-03  2.012e-04  31.122  < 2e-16 ***
## stays_nights                  3.749e-02  3.385e-03  11.076  < 2e-16 ***
## total_guests                  1.189e-01  1.358e-02   8.757  < 2e-16 ***
## mealFull Board                4.647e-01  1.212e-01   3.834 0.000126 ***
## mealHalf Board               -3.239e-01  2.781e-02 -11.645  < 2e-16 ***
## mealNo Meal Plan              2.931e-01  2.544e-02  11.523  < 2e-16 ***
## total_of_special_requests    -6.002e-01  1.200e-02 -50.034  < 2e-16 ***
## distribution_channelDirect   -4.044e-01  5.360e-02  -7.544 4.55e-14 ***
## distribution_channelGDS      -5.012e-01  2.120e-01  -2.364 0.018063 *  
## distribution_channelTA/TO     4.200e-01  4.706e-02   8.926  < 2e-16 ***
## is_repeated_guestTRUE        -1.339e+00  8.209e-02 -16.312  < 2e-16 ***
## previous_cancellations        1.951e+00  5.394e-02  36.164  < 2e-16 ***
## booking_changes              -3.720e-01  1.708e-02 -21.779  < 2e-16 ***
## deposit_typeNon Refund        4.956e+00  1.166e-01  42.496  < 2e-16 ***
## deposit_typeRefundable        2.219e-01  2.381e-01   0.932 0.351529    
## required_car_parking_spaces  -8.755e+01  8.667e+05   0.000 0.999919    
## customer_typeGroup           -1.612e-01  1.802e-01  -0.895 0.371010    
## customer_typeTransient        1.114e+00  5.458e-02  20.414  < 2e-16 ***
## customer_typeTransient-Party  2.881e-01  5.724e-02   5.033 4.82e-07 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## (Dispersion parameter for binomial family taken to be 1)
## 
##     Null deviance: 125017  on 94760  degrees of freedom
## Residual deviance:  85157  on 94740  degrees of freedom
## AIC: 85199
## 
## Number of Fisher Scoring iterations: 11

In-sample Prediction and ROC curve

In-sample prediction và Confusion Matrix với xác suất giới hạn là 0.5

table(predict(finalmodel,type="response") > 0.5)
## 
## FALSE  TRUE 
## 72654 22107

Đường ROC cho tập dữ liệu training

pred.glm0.train<- predict(finalmodel, type="response")
pred <- prediction(pred.glm0.train, as.numeric(train$is_canceled))
perf <- performance(pred, measure="tpr", x.measure="fpr")
plot(perf, colorize=TRUE)

AUC cho tập dữ liệu training

unlist(slot(performance(pred, "auc"), "y.values"))
## [1] 0.8353704

Out-of-sample Prediction and ROC curve

Đường ROC cho tập dữ liệu test

pred.glm0.test<- predict(finalmodel, newdata = test, type="response")
pred <- prediction(pred.glm0.test, as.numeric(test$is_canceled))
perf <- performance(pred, measure="tpr", x.measure="fpr")
plot(perf, colorize=TRUE)

Và AUC cho tập dữ liệu test

unlist(slot(performance(pred, "auc"), "y.values"))
## [1] 0.836245

Kết luận

Mô hình hồi quy logistic dự đoán chính xác việc hủy phòng với độ chính xác 84%.

hotels %>% 
  group_by(arrival_date) %>% 
  count(is_canceled) %>% 
  ggplot(aes(x=arrival_date, y=n, color=is_canceled)) + 
  geom_line( ) + 
  geom_hline(aes(yintercept=mean(n, na.rm=TRUE)), linetype="dashed")+
  scale_x_date(date_breaks= "2 month", date_labels = "%Y %b") + 
  theme(axis.text.x=element_text(angle=45, size=10, hjust=1)) +
  ggtitle("") +
  labs(x="", y="Booking", color="") +
  scale_color_manual(values=c("#1F77B4", "#FF7F0E")) +
  ggtitle("Daily Booking with/wo Cancellation") -> p
ggplotly(p)

SUMMARY

Có 6 vấn đề ban đầu nhóm đã đặt ra. Đầu tiên là xác định các lượt đặt phòng đến từ đầu. Tiếp đó, nhóm bắt đầu xác định tần suất hủy phòng, liệu việc hủy đó có phải là không đến nhận phòng hay không và việc hủy bắt nguồn từ đâu. Xu hướng về mức giá có thể tính cho khách hàng. Nhóm cũng tính toán doanh thu được tạo ra bởi mỗi lượt đặt phòng, đánh giá số tiền doanh thu thu được từ các lượt đặt phòng bị hủy và xác định các quốc gia tạo ra nhiều doanh thu nhất cho mỗi lượt đặt phòng. Cuối cùng, nhóm xây dựng model dự đoán lượt đặt phòng có bị hủy bỏ hay không.

Phương pháp phân tích

Các nguồn đặt phòng đến từ đâu sẽ được xác định bằng cách đếm số lượng đặt phòng liên quan đến từng quốc gia (country) và kênh phân phối (distribution_channel). Tỉ lệ đặt phòng liên quan đến repeated_guest được đánh giá bằng cách sử dụng biểu đồ time-series.
Tần suất hủy phòng sẽ được đánh giá bằng cách đếm số lượng đặt phòng bị hủy theo is_canceled. So sánh tỷ lệ hủy phòng giữa 2 khách sạn và theo thời gian. Xác định tỉ lệ hủy phòng mà không đến nhận phòng dựa theo biến bookings_status. Các nguồn hủy bỏ phòng được xác định bằng cách so sánh tỷ lệ hủy bỏ phòng giữa các kênh phân phối (distribution_channel) và quốc gia (country).
Nhóm sử dụng time-series để xác định xu hướng giá phòng trung bình hằng ngày (adr). Trước khi tạo biểu đồ, nhóm đã tính trung bình adr cho mỗi tháng. Sau đó, nhóm đã tạo mô hình ARIMA để dự đoán giá phòng trung bình hằng ngày mỗi tháng trong tương lai. Mô hình này được sử dụng để đưa ra dự báo 2 năm về giá trị trung bình của giá phòng trung bình hằng ngày cho mỗi tháng.
Doanh thu từ các đặt phòng được lập bảng theo cả khách sạn và năm. Để đánh giá tác động của loại hình đặt cọc (deposit_type) đối với việc tạo doanh thu từ đặt phòng bị hủy, doanh thu cho các đặt phòng bị hủy đã được lập bảng theo tình trạng đặt phòng (reservation_status) và loại hình đặt cọc (deposit_type). Cuối cùng, doanh thu trung bình trên mỗi lượt đặt phòng được tính cho từng quốc gia và được vẽ trên bản đồ thế giới.

Kết quả

Các nguồn đặt phòng đến từ đâu?

Bồ Đào Nha là nước có tỷ lệ đặt phòng lớn nhất trong số tất cả các quốc gia có đặt phòng. Điều này bởi vì các khách sạn nằm ở Bồ Đào Nha nên sẽ có một lượng lớn đặt phòng từ quốc gia này. Trong khi châu Âu có nhiều lượt đặt phòng nhất, khách đến từ mọi châu lục. Điều này thể hiện rằng quảng cáo nên tập trung vào thị trường châu Âu, vì các khách sạn đã thu hút khách từ khu vực này. Các đại lý du lịch và công ty lữ hành là kênh phân phối lớn nhất bằng cách đặt trước cho cả hai khách sạn. Xây dựng quan hệ khách hàng với các thực thể này sẽ là điều cần thiết cho sự thành công của khách sạn.

Khách hàng có quay lại khách sạn hay không?

Chỉ có 3% khách là khách quay lại. Khách quay lại không trực tiếp thúc đẩy đặt phòng lặp lại. Xu hướng theo mùa xuất hiện trong time-series đối với tỷ lệ khách là khách quay lại. Cụ thể, tỷ lệ khách quay lại tăng lên mỗi mùa đông. Điều này cho thấy rằng những khách quay lại ít bị thời tiết mùa đông ngăn cản hơn những khách mới. Quảng cáo được nhắm mục tiêu tới những khách cũ này có thể là một cách hiệu quả để tăng lượng đặt phòng vào mùa đông.

Tần suất hủy bỏ đối với khách hàng và khả năng họ không đến nhận phòng là bao nhiêu?

Tỷ lệ hủy bỏ đặt phòng trong tập dữ liệu này là 37,13%. Khách hầu như luôn thông báo trước cho khách sạn của họ trước khi hủy. Tuy nhiên, khoảng 2,7% số lượt hủy bỏ phòng là không thông báo trước. City Hotel liên tục có nhiều lượt hủy hơn so với Resort Hotel. Hơn nữa, hầu hết các trường hợp hủy bỏ là ở City Hotel. Những xu hướng này cho thấy rằng hành động khắc phục nên được thực hiện tại City Hotel để giảm số lần hủy.

Đại lý du lịch và công ty lữ hành là kênh phân phối có tỷ lệ hủy lớn nhất. Tỷ lệ hủy phòng lần lượt là 45% và ~32% đối với City Hotel và Resort Hotel với kênh này. Mặc dù có tỷ lệ hủy dưới 50%, các đặt phòng được thực hiện bởi các đại lý du lịch và công ty lữ hành chiếm hơn 80% tổng số lượt hủy. Điều này có thể là do tỷ lệ đặt phòng lớn được thực hiện bằng các kênh này. Giảm số lần hủy từ kênh này có thể rất hiệu quả để giảm số lần hủy. Ngoài ra, tỷ lệ hủy có thể được sử dụng để đặt giá nhằm bù đắp tổn thất do hủy.

27.345 lượt hủy là từ các đặt phòng bắt nguồn từ Bồ Đào Nha. Đây là một thứ tự lớn hơn so với quốc gia cao nhất tiếp theo trong danh sách. Tuy nhiên, do số lượng đặt phòng lớn của khách Bồ Đào Nha nên tỷ lệ hủy chỉ ~56,6%. Việc tìm kiếm và loại bỏ nguyên nhân của những lần hủy phòng ở Bồ Đào Nha này sẽ có lợi vì số lượng hủy phòng rất lớn. Các quốc gia ở xa Bồ Đào Nha có xu hướng có tỷ lệ hủy bỏ cao hơn, cũng như các quốc gia nằm ở Nam bán cầu.

Xu hướng về mức giá có thể tính cho khách hàng là bao nhiêu?

Giá phòng trung bình hằng ngày (adr) trung bình hàng tháng hiển thị các xu hướng theo chu kỳ mạnh mẽ khi được vẽ dưới dạng time-series. Nó cao nhất trong những tháng mùa hè và thấp nhất trong những tháng mùa đông. Mô hình ARIMA của nhóm dự báo rằng adr trung bình hàng tháng sẽ giảm vào mùa đông năm 2018 và sau đó chững lại.

Khách sạn có thể kiếm được bao nhiêu doanh thu trên mỗi lần đặt phòng và bao nhiêu tiền đến từ mỗi quốc gia?

City Hotel đã kiếm được nhiều doanh thu hơn so với Resort Hotel trong năm 2016 và 2017, nhưng không phải năm 2015. Nếu các khách sạn đạt được thành công như năm 2015 trong phần còn lại của năm 2017, thì cả hai khách sạn sẽ vượt qua doanh thu của họ trong năm 2016. Các khách sạn đã xoay sở để thu hồi một số doanh thu của họ bị mất do đặt phòng bị hủy.

Hầu hết các quốc gia đã tạo ra doanh thu đặt phòng trung bình ~500€. Ả Rập Saudi, Ai Cập, Ghana, Angola và Nga đều là những quốc gia tạo ra doanh thu trung bình cao trên mỗi lần đặt trước. Điều này cho thấy rằng một chiến dịch quảng cáo ở các quốc gia này sẽ rất sinh lợi.

Hướng phát triển

Hạn chế của bộ dữ liệu này là nó chỉ chứa dữ liệu từ một phần của năm 2015, 2016 và một phần của năm 2017. Điều này gây khó khăn cho việc đánh giá các xu hướng dài hạn. Việc xác định các xu hướng dài hạn sẽ yêu cầu thu thập dữ liệu mới để kéo dài khoảng thời gian được xem xét.

Một hạn chế khác của bộ dữ liệu là chỉ có City Hotel và Resort Hotel. Đưa ra những khái quát về ngành khách sạn ở Bồ Đào Nha từ hai khách sạn này là không hợp lý. Cần lấy một mẫu lớn hơn đến từ các khách sạn ở Bồ Đào Nha để hỗ trợ cho việc phân tích tốt hơn.

Mặc dù nhóm có thể xác định các xu hướng giá phòng trung bình hằng ngày trung bình hàng tháng, nhưng việc dự báo là không khả thi. Nguyên nhân là do mô hình ARIMA của nhóm dựa trên kết quả giá phòng trung bình hàng ngày trung bình hàng tháng trong 26 tháng cho tập dữ liệu này. Nó cho thấy adr trung bình hàng tháng đang giảm khi bắt đầu dự báo, phù hợp với xu hướng theo chu kỳ. Nhưng sau đó nó đạt đến một tiệm cận. Để điều đó xảy ra, một khách sạn sẽ phải áp dụng mức giá thấp cho nhu cầu cao điểm của họ. Rõ ràng, đây là một kết quả phi thực tế. Mở rộng tập dữ liệu sang các khoảng thời gian dài hơn sẽ cho kết quả thực tế hơn. Ngoài ra, hiệu suất của mô hình có thể được cải thiện bằng cách training adr.

Nếu có nhiều thời gian hơn thì nhóm sẽ xây dựng thêm nhiều model dự đoán khác nhau để xem có sự khác biệt nào không.

REFERENCES

[1] N. Antonio, A. de Almeida and L. Nunes, Hotel booking demand datasets, Volume 22, February 2019.

[2] T. Mock and A. Bichat. “Hotels.” rfordatascience / tidytuesday